using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
using TerrainGenerator.Physics;
using JigLibX.Physics;
using JigLibX.Collision;

namespace TerrainGenerator
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;

        Matrix world;
        Matrix view;
        Matrix projection;

        Vector3 viewVector;

        BinaryTriangleTerrain btt;

        TerrainObject terrain;
        HeightmapObject hmap;

        FPSCamera fpsCam;
        ChaseCamera chaseCam;
        AimCam aimCam;

        Aim aim;

        Effect terrainEffect;

        KeyboardState prevKeyboardState = Keyboard.GetState();
        bool wireframe = false;

        GolfBall ball;
        Vector3 ballPosition = new Vector3(256, 120, 256);

        const float stepSize = 1.0f / 100.0f;
        double phase;

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
        }

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            // TODO: Add your initialization logic here
            world = Matrix.CreateTranslation(Vector3.Zero);
            view = Matrix.CreateLookAt(new Vector3(0, 100, 0), new Vector3(64, 0, 64), Vector3.Up);
            projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45), GraphicsDevice.Viewport.AspectRatio, 0.1f, 1000.0f);

            fpsCam = new FPSCamera(GraphicsDevice, new Vector3(0, 100, 0), MathHelper.PiOver2, -MathHelper.Pi / 10.0f);
            chaseCam = new ChaseCamera(GraphicsDevice);
            chaseCam.AspectRatio = GraphicsDevice.Viewport.AspectRatio;
            // Set the camera offsets
            chaseCam.DesiredPositionOffset = new Vector3(0.0f, 40.0f, 70.0f);
            chaseCam.LookAtOffset = new Vector3(0.0f, 0.0f, 0.0f);

            aimCam = new AimCam();

            aim = new Aim(GraphicsDevice);
            
            InitializePhysics();

            phase = 0.0;

            base.Initialize();
        }

        void InitializePhysics()
        {
            PhysicsSystem physics = new PhysicsSystem();
            //physics.SolverType = PhysicsSystem.Solver.Normal;
            physics.CollisionSystem = new CollisionSystemSAP();
            physics.EnableFreezing = true;
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);

            // TODO: use this.Content to load your game content here
            Perlin perlin = new Perlin(0.025f, 5.0f, 3, 0.5f, 2.0f);
            TerrainGenerator terrainGen = new TerrainGenerator(perlin);

            float[,] heightmap = terrainGen.GenerateHeightmap(1025, 1025, 0.1f, 0.1f, 10.0f);

            btt = new BinaryTriangleTerrain(heightmap);
            btt.Generate(GraphicsDevice, 0.75f, fpsCam.Position);

            //terrain = new TerrainObject(this, btt);

            hmap = new HeightmapObject(this, heightmap);

            ball = new GolfBall(this, ballPosition);

            Console.WriteLine("Number of triangles: " + btt.CountTriangles());

            terrainEffect = new TerrainShaderGenerator(this).PerlinPixelShader;

            SetChaseCam();
            aimCam.SetTarget(aim.position);
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here
        }

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            // Allows the game to exit
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();

            KeyboardState keyboardState = Keyboard.GetState();
            if (keyboardState.IsKeyDown(Keys.Space) && prevKeyboardState.IsKeyUp(Keys.Space))
            {
                wireframe = !wireframe;
            }
            
            /*
            // TODO: Add your update logic here
            fpsCam.UpdateCamera(Keyboard.GetState(), Mouse.GetState());
            view = fpsCam.CalculateViewMatrix();

            Vector3 cameraLocation = fpsCam.Position;
            Vector3 cameraTarget = fpsCam.CalculateViewVector();
            viewVector = Vector3.Transform(cameraTarget - cameraLocation, Matrix.CreateRotationY(0));
            viewVector.Normalize();
            */
            SetChaseCam();
            //chaseCam.Update(gameTime, Mouse.GetState());
            aimCam.Update(Keyboard.GetState());
            view = aimCam.GetViewMatrix();
            viewVector = Vector3.Transform(aimCam.lookatPosition - aimCam.camPosition, Matrix.CreateRotationY(0));
            viewVector.Normalize();

            // hit ball
            if (!ball.IsActive())
            {
                if (keyboardState.IsKeyDown(Keys.Enter) && prevKeyboardState.IsKeyUp(Keys.Enter))
                {
                    HitBall();
                }
            }

            phase += (float)gameTime.ElapsedGameTime.TotalSeconds * 2.0f;
            while (phase > 0)
            {
                PhysicsSystem.CurrentPhysicsSystem.Integrate(stepSize);
                phase -= stepSize;
            }

            prevKeyboardState = keyboardState;
            base.Update(gameTime);
        }

        protected void SetChaseCam()
        {
            chaseCam.ChasePosition = ball.Body.Position;
            chaseCam.ChaseDirection = Vector3.UnitZ;
            chaseCam.Up = Vector3.Up;
            aimCam.position = ball.Body.Position;
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);


            GraphicsDevice.RenderState.FillMode = wireframe ? FillMode.WireFrame : FillMode.Solid;
            btt.Draw(world, view, projection, viewVector, terrainEffect);

            //terrain.Draw(world, view, projection, viewVector, terrainEffect);
            
            ball.Draw(world, view, projection, Color.White);

            aim.Draw(world, view, projection);

            base.Draw(gameTime);
        }

        void HitBall()
        {
            Console.WriteLine("hit ball");

            Vector3 hitVector = viewVector;
            hitVector.Y = 1.0f;

            ball.ApplyForce(50.0f, hitVector);
        }
    }
}
